package com.github.elliot.gatewaycenter.security.filters;

import com.github.elliot.domain.PermissionVO;
import com.github.elliot.gatewaycenter.common.ResponseEnum;
import com.github.elliot.gatewaycenter.common.constant.AuthConstant;
import com.github.elliot.gatewaycenter.domain.AdminUserPO;
import com.github.elliot.gatewaycenter.security.converter.UsernamePasswordAuthenticationConverter;
import com.github.elliot.gatewaycenter.security.handlers.CustomServerAuthenticationFailureHandler;
import com.github.elliot.gatewaycenter.security.handlers.CustomServerAuthenticationSuccessHandler;
import com.github.elliot.gatewaycenter.security.service.SecurityUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.HashSet;

public class UsernamePasswordAuthenticationFilter extends AuthenticationWebFilter {

    public UsernamePasswordAuthenticationFilter(AbstractUserDetailsReactiveAuthenticationManager usernamePasswordAuthenticationManager,
                                                CustomServerAuthenticationSuccessHandler customServerAuthenticationSuccessHandler,
                                                CustomServerAuthenticationFailureHandler customServerAuthenticationFailureHandler,
                                                UsernamePasswordAuthenticationConverter customAuthenticationConverter) {
        super(usernamePasswordAuthenticationManager);
        this.setServerAuthenticationConverter(customAuthenticationConverter);
        this.setRequiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(AuthConstant.LOGIN_URL));
        this.setAuthenticationFailureHandler(customServerAuthenticationFailureHandler);
        this.setAuthenticationSuccessHandler(customServerAuthenticationSuccessHandler);
    }

    @Component
    @Primary
    public static class UsernamePasswordAuthenticationManager extends AbstractUserDetailsReactiveAuthenticationManager {

        @Autowired
        private PasswordEncoder passwordEncoder;

        private final SecurityUserDetailService securityUserDetailService;

        public UsernamePasswordAuthenticationManager(SecurityUserDetailService securityUserDetailService) {
            this.securityUserDetailService = securityUserDetailService;
        }

        @Override
        public Mono<Authentication> authenticate(Authentication authentication) {
            // 获取用户的上传的信息
            AdminUserPO adminUserPo = (AdminUserPO) authentication.getPrincipal();
            String username = adminUserPo.getUsername();
            String presentedPassword = (String) authentication.getCredentials();
            return retrieveUser(username)
                    .switchIfEmpty(Mono.defer(() -> Mono.error(new AuthenticationCredentialsNotFoundException(ResponseEnum.USER_NOT_FOUND.getMsg()))))
                    .doOnNext(this::preAuthenticationChecks)
                    .filter(userDetails -> this.passwordEncoder.matches(presentedPassword, userDetails.getPassword()))
                    .switchIfEmpty(Mono.defer(() -> Mono.error(new BadCredentialsException(ResponseEnum.LOGIN_FAILED.getMsg()))))
                    .flatMap(this::createUsernamePasswordAuthenticationToken);
        }

        private Mono<UsernamePasswordAuthenticationToken> createUsernamePasswordAuthenticationToken(UserDetails userDetails) {
            AdminUserPO adminUserPo = (AdminUserPO) userDetails;
            adminUserPo.setPassword(null);
            String userNo = adminUserPo.getUserNo();
            return securityUserDetailService.findBackendUrlByUserNo(userNo)
                    .map(PermissionVO::getBackendUrl)
                    .collectList()
                    .map(backendUrls -> {
                        adminUserPo.setBackendUrls(new HashSet<>(backendUrls));
                        return UsernamePasswordAuthenticationToken.authenticated(adminUserPo, null, userDetails.getAuthorities());
                    });
        }

        @Override
        protected Mono<UserDetails> retrieveUser(String username) {
            return securityUserDetailService.findByUsername(username);
        }

        // 用户状态预校验
        private void preAuthenticationChecks(UserDetails user) {
            if (!((AdminUserPO)user).getStatus().equals(1)) {
                throw new LockedException(ResponseEnum.LOCALED_USER.getMsg());
            }
        }
    }
}
